package cn.sylinx.hbatis.db.common;

import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.sylinx.hbatis.db.cache.CacheKeyGenerator;
import cn.sylinx.hbatis.db.cache.CacheQuery;
import cn.sylinx.hbatis.db.cache.ICacheKit;
import cn.sylinx.hbatis.db.cache.IDataLoader;
import cn.sylinx.hbatis.db.mapper.QueryMapper;
import cn.sylinx.hbatis.exception.HbatisException;
import cn.sylinx.hbatis.kit.StrKit;
import cn.sylinx.hbatis.log.GLog;

/**
 * 缓存查询实现类
 * 
 * @author han
 *
 */
public class DefaultCacheQuery implements CacheQuery {

	/**
	 * DbPro对象
	 */
	private final DbPro dbPro;
	private ICacheKit cacheKit;

	private Map<String, String> cachedKey = new HashMap<>();

	public DefaultCacheQuery(DbPro dbPro) {
		this.dbPro = dbPro;
		this.cacheKit = ICacheKit.create();
	}

	public String getSqlMD5(String key) {

		if (cachedKey.containsKey(key)) {
			return cachedKey.get(key);
		}

		if (StrKit.isEmpty(key)) {
			return "";
		}

		MessageDigest digest = null;
		byte buffer[] = key.getBytes(Charset.defaultCharset());

		try {
			digest = MessageDigest.getInstance("MD5");
			digest.update(buffer);
		} catch (Exception e) {
			GLog.error("md5 encode error", e);
			throw new HbatisException(e);
		}

		BigInteger bigInt = new BigInteger(1, digest.digest());
		String ecodeStr = bigInt.toString(16);
		cachedKey.put(key, ecodeStr);

		return ecodeStr;
	}

	@Override
	public Record queryFirstRecord(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryFirstRecord(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q1", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q1]: 'public Record queryFirstRecord(final String sql, final Object... params)' , cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public Record load() {
				return dbPro.queryFirstRecord(sql, params);
			}
		});
	}

	@Override
	public List<Record> queryRecords(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryRecords(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q2", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q2]: 'public List<Record> queryRecords(final String sql, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public List<Record> load() {
				return dbPro.queryRecords(sql, params);
			}
		});
	}

	@Override
	public List<Object[]> query(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.query(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q3", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q3]: 'public List<Object[]> query(final String sql, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public List<Object[]> load() {
				return dbPro.query(sql, params);
			}
		});
	}

	@Override
	public List<Map<String, Object>> queryMap(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryMap(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q4", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q4]: 'public List<Map<String, Object>> queryMap(final String sql, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public List<Map<String, Object>> load() {
				return dbPro.queryMap(sql, params);
			}
		});
	}

	@Override
	public Object[] queryFirst(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryFirst(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q5", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q5]: 'public Object[] queryFirst(final String sql, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public Object[] load() {
				return dbPro.queryFirst(sql, params);
			}
		});
	}

	@Override
	public Map<String, Object> queryFirstMap(final String sql, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryFirstMap(sql, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q6", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q6]: 'public Map<String, Object> queryFirstMap(final String sql, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public Map<String, Object> load() {
				return dbPro.queryFirstMap(sql, params);
			}
		});
	}

	@Override
	public <T> List<T> query(final String sql, final QueryMapper<T> mapper, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.query(sql, mapper, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q7", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q7]: 'public <T> List<T> query(final String sql, final QueryMapper<T> mapper, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public List<T> load() {
				return dbPro.query(sql, mapper, params);
			}
		});
	}

	@Override
	public <T> T queryFirst(final String sql, final QueryMapper<T> mapper, final Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryFirst(sql, mapper, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q8", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q8]: 'public <T> T queryFirst(final String sql, final QueryMapper<T> mapper, final Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public T load() {
				return dbPro.queryFirst(sql, mapper, params);
			}
		});
	}

	@Override
	public <T> List<T> queryObject(String sql, Class<T> clazz, Object... params) {

		if (!cacheKit.isInited()) {
			GLog.debug("no cache config, query from db");
			return dbPro.queryObject(sql, clazz, params);
		}

		Object key = CacheKeyGenerator.generateCacheKey(dbPro.getDataSourceName() + "Q9", sql, params);

		String cacheKey = getSqlMD5(String.valueOf(key));
		GLog.debug(
				"use cache query[Q9]: 'public <T> List<T> queryObject(String sql, Class<T> clazz, Object... params)', cache key:{}",
				cacheKey);

		return cacheKit.get(cacheKey, new IDataLoader() {
			@Override
			public List<T> load() {
				return dbPro.queryObject(sql, clazz, params);
			}
		});
	}

	@Override
	public <T> T queryFirstObject(String sql, Class<T> clazz, Object... params) {
		List<T> dataList = queryObject(sql, clazz, params);
		return dataList.isEmpty() ? null : dataList.get(0);
	}

}
